﻿using System;
using System.Collections.Generic;

namespace SIR.Color
{
	/// <summary>
	/// Color spectrum values plus black and white
	/// </summary>
	public enum ColorIndex
	{
		Black = 0,
		Red = 1,
		Yellow = 2,
		Magenta = 3,
		Green = 4,
		Cyan = 5,
		Blue = 6,
		White = 7
	}

	/// <summary>
	/// Combines a color vector (3-d cube) with the HBS values of that color and it's X,Y position in the image
	/// </summary>
	public class PixelColor : ColorDistanceVector
	{
		/// <summary>
		/// Creates a new PixelColor object
		/// </summary>
		/// <param name="c">The color object</param>
		/// <param name="x">The horizontal location</param>
		/// <param name="y">The vertical location</param>
		public PixelColor(System.Drawing.Color c, int x, int y)
			: base(c.R, c.G, c.B)
		{
			Point = new System.Drawing.Point(x, y);
			Hue = c.GetHue();
			Brightness = c.GetBrightness() * 100;
			Saturation = c.GetSaturation() * 100;
		}

		/// <summary>
		/// The pixel location
		/// </summary>
		public System.Drawing.Point Point { get; set; }
		/// <summary>
		/// The hue value from 0 to 360
		/// </summary>
		public double Hue { get; private set; }
		/// <summary>
		/// The brightness of the color from 0 to 100
		/// </summary>
		public double Brightness { get; private set; }
		/// <summary>
		/// The saturation of the color from 0 to 100
		/// </summary>
		public double Saturation { get; private set; }
	}

	/// <summary>
	/// A color vector containing the distances from each corner of the color spectrum cube
	/// </summary>
	public class ColorDistanceVector : ColorVector
	{
		public static readonly double MaximumDistance = 441.67;
		public static readonly double MaximumDistanceOnePercent = MaximumDistance / 100;
		public static readonly double MaximumDistanceTenPercent = MaximumDistance / 10;

		/// <summary>
		/// Creates a new ColorDistanceVector
		/// </summary>
		/// <param name="r">The red value</param>
		/// <param name="g">The green value</param>
		/// <param name="b">The blue value</param>
		public ColorDistanceVector(int r, int g, int b)
			: base(r, g, b)
		{
			DistanceFrom = new double[8];
			foreach (ColorIndex ci in System.Enum.GetValues(typeof(ColorIndex)))
			{
				DistanceFrom[(int)ci] = Distance(this, _colors[(int)ci]);
			}
		}

		public double[] DistanceFrom { get; private set; }
	}

	/// <summary>
	/// A three dimensional vector within a color spectrum cube
	/// </summary>
	public class ColorVector : Vector3<int>
	{
		// The outter bounds of the cube with the same properies as a color wheel
		private static readonly ColorVector _black = new ColorVector(0, 0, 0);
		private static readonly ColorVector _red = new ColorVector(255, 0, 0);
		private static readonly ColorVector _yellow = new ColorVector(255, 255, 0);
		private static readonly ColorVector _magenta = new ColorVector(255, 0, 255);
		private static readonly ColorVector _green = new ColorVector(0, 255, 0);
		private static readonly ColorVector _cyan = new ColorVector(0, 255, 255);
		private static readonly ColorVector _blue = new ColorVector(0, 0, 255);
		private static readonly ColorVector _white = new ColorVector(255, 255, 255);
		internal static readonly ColorVector[] _colors = new[]
			{
				_black, _red, _yellow, _magenta, _green, _cyan, _blue, _white
			};

		/// <summary>
		/// Creates a new color vector
		/// </summary>
		/// <param name="r">The red value</param>
		/// <param name="g">The green value</param>
		/// <param name="b">The blue value</param>
		public ColorVector(int r, int g, int b) :
			base(r, g, b)
		{

		}

		/// <summary>
		/// The Red value from 0 to 255
		/// </summary>
		public int R { get { return X; } }
		/// <summary>
		/// The Green value from 0 to 255
		/// </summary>
		public int G { get { return Y; } }
		/// <summary>
		/// The Blue value from 0 to 255
		/// </summary>
		public int B { get { return Z; } }

		/// <summary>
		///  Calculates the distance between two colors
		/// </summary>
		/// <param name="a">From point</param>
		/// <param name="b">To point</param>
		/// <returns></returns>
		public static double Distance(ColorVector a, ColorVector b)
		{
			return Math.Sqrt(Math.Pow(a.R - b.R, 2) + Math.Pow(a.G - b.G, 2) + Math.Pow(a.B - b.B, 2));
		}
	}

	/// <summary>
	/// A three dimenational vector (x, y, z)
	/// </summary>
	/// <typeparam name="T">The type of the dimensions of the vector</typeparam>
	public class Vector3<T>
	{
		/// <summary>
		/// 
		/// </summary>
		/// <param name="x">Horizontal</param>
		/// <param name="y">Vertical</param>
		/// <param name="z">Depth</param>
		public Vector3(T x, T y, T z)
		{
			X = x;
			Y = y;
			Z = z;
		}

		/// <summary>
		/// The x-dimension
		/// </summary>
		public T X { get; private set; }
		/// <summary>
		/// The y-dimension
		/// </summary>
		public T Y { get; private set; }
		/// <summary>
		/// The z-dimension
		/// </summary>
		public T Z { get; private set; }
	}

	/// <summary>
	/// A 2 dimentional matrix containing a list of items
	/// </summary>
	/// <typeparam name="T">The type of items contained within each list</typeparam>
	public class ListMatrix2<T> : Matrix2<List<T>>
	{
		/// <summary>
		/// Creates a new ListMatrix with the specified size
		/// </summary>
		/// <param name="size">The size of the matrix</param>
		public ListMatrix2(int size)
			: base(size)
		{

		}

		/// <summary>
		/// Adds an item to the list at the specific x,y location in the matrix 
		/// </summary>
		/// <param name="item">The item to be added</param>
		/// <param name="x">The x dimension</param>
		/// <param name="y">The y dimension</param>
		public void Add(T item, int x, int y)
		{
			// Create the list if it doesn't exist yet
			if (this[x, y] == null)
				this[x, y] = new List<T>() { item };
			else
				this[x, y].Add(item);
		}

		/// <summary>
		/// Turn the matrix into a list of all of the items within each list
		/// </summary>
		/// <returns>A single list containing all of the items within each list contained in the matrix</returns>
		public new List<T> ToList()
		{
			var allLists = base.ToList();
			var allItems = new List<T>();
			foreach (var list in allLists)
			{
				allItems.AddRange(list);
			}
			return allItems;
		}
	}

	/// <summary>
	/// A 2 dimentional matrix of items
	/// </summary>
	/// <typeparam name="T">The type of the item contained at each point in the matrix</typeparam>
	public class Matrix2<T>
	{
		/// <summary>
		/// Creates a new matrix with the specified size
		/// </summary>
		/// <param name="size">The size of the matrix</param>
		public Matrix2(int size)
		{
			// Construct the matrix x by y as a 
			// list of lists with the default of the type
			for (int ii = 0; ii < size; ++ii)
			{
				var row = new List<T>();
				_items.Add(row);
				for (int jj = 0; jj < size; ++jj)
				{
					row.Add(default(T));
				}
			}
		}

		// List of lists containing each item in the matrix
		private List<List<T>> _items = new List<List<T>>();

		/// <summary>
		/// Gets the item contained at the specified location in the matrix
		/// </summary>
		/// <param name="x">The x dimension of the matrix</param>
		/// <param name="y">The y dimension of the matrix</param>
		/// <returns>The item contained at the specified (x,y) location</returns>
		public T this[int x, int y]
		{
			get
			{
				return _items[x][y];
			}

			protected set
			{
				_items[x][y] = value;
			}
		}

		/// <summary>
		/// Returns the list of items contained within the matrix in a one dimension form
		/// </summary>
		/// <returns>All of the items contained within the matrix</returns>
		public List<T> ToList()
		{
			var allItems = new List<T>();

			foreach (var list in _items)
			{
				allItems.AddRange(list);
			}
			return allItems;
		}
	}
}
